package org.example

import java.io.File
import org.w3c.dom.Element
import java.util.Scanner
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult

val rootDir = File(System.getProperty("user.dir"))

//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
fun main() {
    println("根目录：${rootDir.absolutePath}")

    val apkFile = File(rootDir, "mubao/mubao.apk")
    val apktoolJar = File(rootDir, "tools/apktool.jar")
    val decompileDir = File(rootDir, "decompile_build")

    if (decompileDir.exists()) {
        //删除上次编译的目录
        decompileDir.delete()
    }
    //创建反编译输出目录
    decompileDir.mkdirs()

    if (!apkFile.exists()) {
        println("未找到母包 APK 文件：${apkFile.absolutePath}")
        return
    }
    if (!apktoolJar.exists()) {
        println("未找到 apktool JAR 文件：${apktoolJar.absolutePath}")
        return
    }

    println("开始反编译 APK...")
    val cmd = listOf(
        "java", "-jar", apktoolJar.absolutePath,
        "d", "-f",
        "-o", decompileDir.absolutePath,
        apkFile.absolutePath
    )

    val process = ProcessBuilder(cmd)
        .directory(rootDir)
        .inheritIO()
        .start()

    val code = process.waitFor()
    if (code == 0) {
        println("✅ 反编译成功，输出目录：${decompileDir.absolutePath}")
        val appName = resolveAppName(decompileDir)
        println("应用名称：${appName}")
        filterSmaliConflicts(decompileDir)
        filterRes(decompileDir)
        generateMinimalStringsXml(appName,decompileDir)
        smaliToDex(decompileDir)
        dexToJar(decompileDir)
        copyTemplateProject(decompileDir)
    } else {
        println("❌ 反编译失败，退出码：$code")
    }
}

/**
 * 获取应用名称
 */
fun resolveAppName(decompileDir: File): String {
    //从控制台获取用户指定的名称
    println("请输入应用名称：(回车键跳过，默认使用母包中的名称)")
    val scanner = Scanner(System.`in`)
    val inputAppName = scanner.nextLine().trim()
    if (inputAppName.isNotEmpty()){
        return inputAppName
    }
    println("用户未输出应用名称，正在从母包中获取")
    //从母包中读取
    val stringsXml = File(decompileDir, "res/values/strings.xml")
    if (!stringsXml.exists()){
        throw RuntimeException("母包中没有strings.xml文件")
    }

    val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
    val doc = docBuilder.parse(stringsXml)
    val nodes = doc.getElementsByTagName("string")

    for (i in 0 until nodes.length) {
        val item = nodes.item(i) as? Element ?: continue
        if (item.getAttribute("name") == "app_name") {
            return item.textContent.trim()
        }
    }
    //没有读取到母包中的string.xml的app_name 抛出异常程序中止
    throw RuntimeException("strings.xml中没有检索到app_name配置")

}

/**
 * 生成strings.xml，在 filterRes() 之后调用
 */
fun generateMinimalStringsXml(appName: String, decompileDir: File) {
    val valuesDir = File(decompileDir, "res/values")
    if (!valuesDir.exists()) valuesDir.mkdirs()

    val stringsXml = File(valuesDir, "strings.xml")
    val content = """
        |<?xml version="1.0" encoding="utf-8"?>
        |<resources>
        |    <string name="app_name">${appName}</string>
        |</resources>
    """.trimMargin()

    stringsXml.writeText(content)
    println("✅ 已生成精简版 strings.xml，仅保留 app_name = \"$appName\"")
}

/**
 * 删除冲突包的 smali 文件夹
 * 主要是Android系统库 和 sdk，二次打包时会重新加入的
 */
fun filterSmaliConflicts(decompileDir: File) {
    // 支持多个 smali 文件夹（如 smali, smali_classes2, smali_classes3...）
    val smaliDirs = decompileDir.listFiles()?.filter { it.isDirectory && it.name.startsWith("smali") } ?: return

    // 要删除的包前缀（用 / 分隔路径）
    val deletePaths = listOf(
        "android",
        "androidx",
        "com/google",
        "kotlin",
        "kotlinx",
        "org/intellij/lang/annotations",
        "org/jetbrains/annotations",
        "com/example/game/sdk" // 你可自定义 SDK 包路径
    )

    println("🔍 正在过滤冲突 smali 目录...")
    for (smaliDir in smaliDirs) {
        for (path in deletePaths) {
            val folder = File(smaliDir, path)
            if (folder.exists()) {
                folder.deleteRecursively()
                println("🗑️ 删除：${folder.absolutePath}")
            }
        }
    }

    println("✅ 冲突类过滤完成。")
}


/**
 * res资源过滤
 * 一般用不上，因为游戏几乎不会往Android工程中添加资源，游戏的资源都在assets目录
 */
fun filterRes(decompileDir: File) {
    val resDir = File(decompileDir, "res")
    if (!resDir.exists()) {
        println("⚠️ 未找到 res 目录，跳过资源过滤。")
        return
    }
    //,"strings.xml","ids.xml","ic_launcher" 为了解决问题 临时接入，实际上游戏母包是不需要的
    //ic_launcher 启动图片可以通过设计一个上传启动图的功能
    //strings.xml为了保存 app name，正常的逻辑：在用户没有主动配置 appName时 复用 母包appName
    //ids.xml 由于使用了Android 资源 需要保存id，但正常游戏是不会使用Android 资源的
    val retainPrefixes = listOf("game_", "ids.xml", "ic_launcher") //可添加更多前缀，

    println("🔍正在过滤 res 资源目录，保留前缀为 ${retainPrefixes.joinToString()} 的文件...")

    // 1. 删除不符合前缀的文件
    resDir.walkTopDown().forEach { file ->
        if (file.isFile) {
            val fileName = file.name
            val shouldKeep = retainPrefixes.any { prefix -> fileName.startsWith(prefix) }
            println("🗑️ 删除资源文件：${file.absolutePath}")
            if (!shouldKeep) {
                file.delete()
            }

        }
    }

    // 2. 删除空文件夹
    resDir.walkBottomUp()
        .filter { it.isDirectory && it.listFiles()?.isEmpty() == true }
        .forEach { dir ->
            dir.delete()
            println("🗑️ 删除空目录：${dir.absolutePath}")
        }

    // 3. 如果整个 res 目录为空，删除它
    if (resDir.exists() && resDir.listFiles()?.isEmpty() == true) {
        resDir.delete()
        println("🧹 删除空的 res 目录：${resDir.absolutePath}")
    }

    println("✅ 资源过滤完成，仅保留指定前缀的资源文件。")
}



/**
 * smali to dex
 */
fun smaliToDex(decompileDir: File) {
    val smaliJar = File(rootDir, "tools/smali.jar")

    if (!smaliJar.exists()) {
        println("❌ 未找到 smali 工具：${smaliJar.absolutePath}")
        return
    }

    val buildDexDir = File(decompileDir, "dex")
    if (buildDexDir.exists()) {
        buildDexDir.deleteRecursively()
    }
    buildDexDir.mkdirs()

    // 查找所有 smali 开头的目录（smali, smali_classes2 等）
    val smaliDirs = decompileDir.listFiles()
        ?.filter { it.isDirectory && it.name.startsWith("smali") }
        ?: emptyList()

    if (smaliDirs.isEmpty()) {
        println("❌ 未找到任何 smali 目录。")
        return
    }

    println("🔧 开始将 smali 编译为 dex...")

    for ((index, smaliDir) in smaliDirs.withIndex()) {
        val dexFile = File(buildDexDir, "classes${if (index == 0) "" else index + 1}.dex")

        val cmd = listOf(
            "java", "-jar", smaliJar.absolutePath,
            "assemble",
            "-o", dexFile.absolutePath,
            smaliDir.absolutePath
        )

        val process = ProcessBuilder(cmd)
            .directory(smaliDir)
            .inheritIO()
            .start()

        val code = process.waitFor()
        if (code == 0) {
            println("✅ 编译成功：${dexFile.name}")
        } else {
            println("❌ 编译失败：${dexFile.name}，退出码：$code")
        }
    }

    println("🎉 所有 smali 目录已成功编译为 dex，输出目录：${buildDexDir.absolutePath}")
}


/**
 * dex to jar
 */
fun dexToJar(decompileDir: File) {
    val dexDir = File(decompileDir, "dex")
    val jarOutputDir = File(decompileDir, "jar")
    if (jarOutputDir.exists()) jarOutputDir.deleteRecursively()
    jarOutputDir.mkdirs()

    val dexFiles = dexDir.listFiles { file -> file.extension == "dex" } ?: emptyArray()
    if (dexFiles.isEmpty()) {
        println("⚠️ 未找到 dex 文件，跳过 dex2jar 步骤。")
        return
    }

    val isWindows = System.getProperty("os.name").lowercase().contains("windows")
    val scriptName = if (isWindows) "d2j-dex2jar.bat" else "d2j-dex2jar.sh"
    val scriptFile = File(rootDir, "tools/dex-tools/$scriptName")

    if (!scriptFile.exists()) {
        println("❌ 未找到 dex2jar 脚本：${scriptFile.absolutePath}")
        return
    }

    println("🔄 开始将 dex 转换为 jar（使用脚本）...")

    for (dexFile in dexFiles) {
        val outputJarName = dexFile.nameWithoutExtension + ".jar"
        val outputJarFile = File(jarOutputDir, outputJarName)

        val cmd = if (isWindows) {
            listOf("cmd", "/c", scriptFile.absolutePath, dexFile.absolutePath, "-o", outputJarFile.absolutePath)
        } else {
            listOf("sh", scriptFile.absolutePath, dexFile.absolutePath, "-o", outputJarFile.absolutePath)
        }

        val process = ProcessBuilder(cmd)
            .directory(dexDir)
            .inheritIO()
            .start()

        val code = process.waitFor()
        if (code == 0) {
            println("✅ 成功生成 Jar：${outputJarFile.name}")
        } else {
            println("❌ 转换失败：${dexFile.name}，退出码：$code")
        }
    }

    println("🎉 所有 dex 文件已转换为 jar，输出目录：${jarOutputDir.absolutePath}")
}

/**
 * 复制gradle打包工程模版
 */
fun copyTemplateProject(decompileDir: File) {
    val templateDir = File(rootDir, "template_project")
    val gradleAppDir = File(decompileDir, "template_project")
    templateDir.copyRecursively(gradleAppDir, overwrite = true)
    println("✅ 已复制打包模板工程到：${gradleAppDir.absolutePath}")

    injectDecompiledContent(decompileDir, gradleAppDir)
    buildGradleApk(gradleAppDir)
}

/**
 * 反编译的资源注入到打包工程
 */
fun injectDecompiledContent(decompileDir: File, gradleAppDir: File) {
    val appDir = File(gradleAppDir, "app")
    val srcMain = File(appDir, "src/main")

    //注入配置
    injectGradleProperties(
        propsFile = File(gradleAppDir, "gradle.properties"),
        props = mapOf(
            "APP_ID" to "com.example.myapplication",
            "MIN_SDK" to "24",
            "TARGET_SDK" to "35",
            "VERSION_CODE" to "1",
            "VERSION_NAME" to "1.0.0",
            "signing.storeFile" to File(rootDir, "keystore/test.jks").absolutePath.replace(File.separator, "/"),
            "signing.storePassword" to "123456",
            "signing.keyAlias" to "key",
            "signing.keyPassword" to "123456"
        )
    )

    //复制manifest
    copyManifest(decompileDir, srcMain)

    // 复制 res、assets
    copyResource(decompileDir, srcMain)

//    copyUploadedIcons(gradleAppDir)

    // 拷贝所有 JAR 到 libs/
    copyJar(decompileDir, appDir)

    println("✅ 注入母包资源完成")
}

fun copyManifest(decompileDir: File, srcMain: File) {
    // 替换 Manifest
    val manifestFile = File(srcMain, "AndroidManifest.xml")
    File(decompileDir, "AndroidManifest.xml").copyTo(manifestFile, overwrite = true)
    println("✅ 复制 AndroidManifest")

    // 解析 XML
    val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
    val document = docBuilder.parse(manifestFile)
    document.documentElement.normalize()

    val manifest = document.documentElement as Element

    // 需要移除的属性名列表（带命名空间前缀的）
    val attributesToRemove = listOf(
        "android:compileSdkVersion",
        "android:compileSdkVersionCodename",
        "package",
        "platformBuildVersionCode",
        "platformBuildVersionName"
    )

    // 移除属性
    for (attr in attributesToRemove) {
        manifest.removeAttribute(attr)
    }

    // 写回文件
    val transformer = TransformerFactory.newInstance().newTransformer()
    transformer.setOutputProperty(OutputKeys.INDENT, "yes")
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
    transformer.transform(DOMSource(document), StreamResult(manifestFile))

    println("🧹 Manifest 属性清理完毕：$manifestFile")

}

fun copyResource(decompileDir: File, srcMain: File) {
    listOf("res", "assets").forEach { name ->
        val from = File(decompileDir, name)
        val to = File(srcMain, name)
        if (from.exists()) {
            if (to.exists()) to.deleteRecursively()
            from.copyRecursively(to, overwrite = true)
        }
    }
    println("✅ 复制 res assets")

}

fun copyJar(decompileDir: File, appDir: File) {
    val jarDir = File(decompileDir, "jar")
    val libsDir = File(appDir, "libs")
    libsDir.mkdirs()

    if (jarDir.exists()) {
        jarDir.listFiles { file -> file.extension == "jar" }?.forEach { jarFile ->
            val destFile = File(libsDir, jarFile.name)
            jarFile.copyTo(destFile, overwrite = true)
            println("✅ 拷贝 jar 文件：${jarFile.name}")
        }
    } else {
        println("⚠️ 未找到 jar 目录：${jarDir.absolutePath}")
    }
}

fun copyUploadedIcons(targetProjectDir: File) {
    val iconSourceDir: File = File(rootDir, "icons")
    val resTargetDir = File(targetProjectDir, "app/src/main/res")
    if (!iconSourceDir.exists()) {
        println("⚠️ 未提供图标目录，跳过图标复制")
        return
    }

    iconSourceDir.walkTopDown().filter { it.isFile }.forEach { icon ->
        val relative = icon.relativeTo(iconSourceDir)
        val dest = File(resTargetDir, relative.path)
        dest.parentFile.mkdirs()
        icon.copyTo(dest, overwrite = true)
        println("🎨 复制图标资源：${dest.path}")
    }
}

fun injectGradleProperties(propsFile: File, props: Map<String, String>) {
    val existing = if (propsFile.exists()) propsFile.readLines().toMutableList() else mutableListOf()
    val updated = existing.filterNot { line -> props.keys.any { line.startsWith("$it=") } }.toMutableList()
    props.forEach { (key, value) ->
        updated.add("$key=$value")
    }
    propsFile.writeText(updated.joinToString("\n"))
    println("✅ 已更新 gradle.properties")
}


/**
 * 打包
 */
fun buildGradleApk(projectDir: File) {
    val isWindows = System.getProperty("os.name").lowercase().contains("win")
    val gradlewName = if (isWindows) "gradlew.bat" else "gradlew"
    val gradlewFile = File(projectDir, gradlewName)

    if (!isWindows) {
        // 添加执行权限
        ProcessBuilder("chmod", "+x", gradlewFile.absolutePath)
            .inheritIO()
            .start()
            .waitFor()
    }

    val gradlewCmd = if (isWindows) gradlewFile.name else "./${gradlewFile.name}"

    val process = ProcessBuilder(gradlewCmd, "assembleRelease")
        .directory(projectDir)
        .inheritIO()
        .start()

    val result = process.waitFor()
    if (result == 0) {
        println("✅ APK 打包成功")
    } else {
        println("❌ APK 打包失败")
    }
}